/*
 * Copyright (C) 2013 Ted Kosan
 * 
 * This file is part of MathPiper Exercise.
 * 
 * Foobar is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 * 
 * Foobar is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Foobar. If not, see <http://www.gnu.org/licenses/>.
*/

package org.mathpiper.ui.gui.android;

//import org.mathpiper.android.R;
import java.io.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.mathpiper.interpreters.EvaluationResponse;
import org.mathpiper.test.Fold;
import org.mathpiper.ui.gui.android.ExerciseService.LocalBinder;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.text.Editable;
import android.util.Log;
import android.view.View;
import android.view.KeyEvent;
import android.view.View.OnKeyListener;
import android.widget.Button;
import android.widget.ToggleButton;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MathPiperExerciseActivity extends Activity implements
		OnSharedPreferenceChangeListener, OnUtteranceCompletedListener,
		ServiceConnection, RecognitionListener {

	private TextView timerText;
	private EditText messageText;
	private EditText problemText;
	private Editable problemTextPauseStorage;
	private EditText inputTextField;

	private static final int REQUEST_CODE = 1234;

	private ExerciseService exerciseService;

	private QuestionManager currentQuestionManager;

	private SharedPreferences preferences;

	private Map<String, Fold> foldsMap;

	private static final String TAG = "MathPiperExerciseActivity";

	private int utteranceNumber = 0;

	private boolean started = false;

	private boolean isSpeech = false;

	private boolean isListen = false;

	private Handler questionHandler = new Handler();

	private Handler timerHandler = new Handler();

	private long problemDelay;

	private long questionNumber;

	private String exerciseLogFilename;

	private FileWriter exerciseLogOutputWriter;

	private boolean questionJustAsked = false;

	private TimerManager timerManager;

	private ToggleButton button_pause_resume;

	private int correctOnFirstTryCount = 0;

	private boolean newQuestionFlag = false;

	private ToggleButton button_start_stop;

	private Pattern p;

    private Intent speechRecognitionIntent;

    private SpeechRecognizer speechRecognizer;

    private static Map<String, String> wordMap = new java.util.HashMap();

    static{
        wordMap.put("zero","0");

        wordMap.put("one","1");

        wordMap.put("two","2");
        wordMap.put("to","2");
        wordMap.put("too","2");

        wordMap.put("three","3");
        wordMap.put("free","3");

        wordMap.put("four","4");
        wordMap.put("for","4");

        wordMap.put("five","5");
        wordMap.put("fine","5");

        wordMap.put("six","6");
        wordMap.put("sex","6");

        wordMap.put("seven","7");

        wordMap.put("eight","8");
        wordMap.put("ate","8");
        wordMap.put("eat","8");

        wordMap.put("nine","9");
        wordMap.put("mine","9");


    }

	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
			String key) {

		String exercisePackage = exerciseService
				.getGlobalString("exercisePackage");

		if (key.equals(exercisePackage + "." + "speak:Boolean")) {
			isSpeech = sharedPreferences.getBoolean(key, false);
			log("[\"Tag\",\"PreferenceChange\"]"
					+ preferencesToMathPiperCode(key, String.valueOf(isSpeech)));
			return;
		} else if (key.equals(exercisePackage + "." + "listen:Boolean")) {
				isListen = sharedPreferences.getBoolean(key, false);
				log("[\"Tag\",\"PreferenceChange\"]"
						+ preferencesToMathPiperCode(key, String.valueOf(isListen)));
			if(isListen)
			{
			    PackageManager pm = getPackageManager();
			    List<ResolveInfo> activities = pm.queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
			    if (activities.size() == 0) { //Note: disable listen preference if the recognizer is not found.
        			    Toast.makeText(getApplicationContext(), "Recognizer Not Found", Toast.LENGTH_SHORT).show();
			    }
			}

			return;
		} else if (key.equals(exercisePackage + "." + "problemDelay:Integer")) {
			String problemDelayString = sharedPreferences.getString(
					exercisePackage + "." + "problemDelay:Integer", "2000");

			try {
				problemDelay = Long.parseLong(problemDelayString);
			} catch (Throwable t) {
				t.printStackTrace();
			}
		} else {

			String value = sharedPreferences.getString(key, "Undefined");

			String variableNameWithoutPackage = key.substring(
					key.lastIndexOf(".") + 1, key.length());

			assignPreferencesToVariables(variableNameWithoutPackage, value);
		}

		log("[\"Tag\",\"PreferenceChange\"]"
				+ preferencesToMathPiperCode(key,
						sharedPreferences.getString(key, "Undefined")));
	}

	public void assignPreferencesToVariables(String variableName, String value) {

		try {

			String[] variableNameAndTypeArray = variableName.split(":");

			String name = variableNameAndTypeArray[0];

			String type = variableNameAndTypeArray[1];

			if (type.contains("String")) {
				value = "\"" + value + "\"";
			}

			String exercisePackage = exerciseService
					.getGlobalString("exercisePackage");

			Log.d(TAG, "Preference change. Key: " + exercisePackage + "."
					+ name + "  Type: " + type + "  Value: " + value);

			String code = name + " := " + value + ";";

			EvaluationResponse response = exerciseService.evaluate(code);

			String result;

			if (response.isExceptionThrown()) {
				result = response.getException().getMessage();

				Log.e(TAG, result);

				Toast.makeText(MathPiperExerciseActivity.this, result,
						Toast.LENGTH_SHORT).show();
			}

		} catch (Throwable e) {
			Log.e(TAG, e.getMessage());

			Toast.makeText(MathPiperExerciseActivity.this, e.getMessage(),
					Toast.LENGTH_SHORT).show();

		}
	}

	private void initializePreferences() {

		// todo:tk:it will probably be safer to use the names of keys that are
		// currently saved as
		// preferences instead of obtaining these names from the .mpw file. This
		// is in case the
		// .mpw file is changed while the application is still active.
		EvaluationResponse response = exerciseService
				.evaluate("ConfigurationsGet();");

		String result;

		String exercisePackage = exerciseService
				.getGlobalString("exercisePackage");

		if (response.isExceptionThrown()) {
			Log.e(TAG, response.getException().getMessage());
			messageText.append(response.getException().getMessage() + ".\n");
		} else {
			result = response.getResult();

			// result = result.replace("\"", "");

			String[] configurations = result.split(";");

			for (String configuration : configurations) {
				String[] values = configuration.split("\\|");

				String name = values[0].trim();

				String type = values[1].trim();

				String value = preferences.getString(exercisePackage + "."
						+ name + ":" + type, values[2].trim());

				this.assignPreferencesToVariables(name + ":" + type, value);

			}

		}// end else.

		isSpeech = preferences.getBoolean(exercisePackage + "."
				+ "speak:Boolean", false);

		isListen = preferences.getBoolean(exercisePackage + "."
				+ "listen:Boolean", false);

	}// end method.

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

        // this.speechRecognizer = SpeechRecognizer.createSpeechRecognizer(getApplicationContext());
        // this.speechRecognizer.setRecognitionListener(this);
        speechRecognitionIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        speechRecognitionIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        speechRecognitionIntent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, 500);
        speechRecognitionIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
        speechRecognitionIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Speak...");
        speechRecognitionIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());

		this.exerciseLogFilename = Environment.getExternalStorageDirectory().getPath() + "/" + "mathpiper_exercise_log.mpi";

		Log.d(TAG, "LogFilename: "+ exerciseLogFilename);

		String regex = "\"(\\([^)]*\\)|[^\"])*\"";
		p = Pattern.compile(regex);


		preferences = PreferenceManager.getDefaultSharedPreferences(this);

		// preferences.edit().clear().apply();

		Intent intent = new Intent(this, ExerciseService.class);
		bindService(intent, this, Context.BIND_AUTO_CREATE);

		preferences.registerOnSharedPreferenceChangeListener(this);

		// mSharedPreferences = getSharedPreferences(MySharedPreferences.NAME,
		// Context.MODE_PRIVATE);

		// =================================================================

		setContentView(R.layout.mathpiperexercise);

		inputTextField = (EditText) findViewById(R.id.inputText);

		inputTextField.setInputType(0);

		inputTextField.setOnKeyListener(new OnKeyListener() {
			public boolean onKey(View v, int keyCode, KeyEvent event) {

				if ((event.getAction() == KeyEvent.ACTION_DOWN)
						&& (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER)) {

					enter();

					return true;
				}

				if (MathPiperExerciseActivity.this.questionJustAsked) {
					log("[\"Tag\",\"AnswerStart\"]");

					MathPiperExerciseActivity.this.questionJustAsked = false;
				}

				return false;
			}
		});

		timerText = (TextView) findViewById(R.id.timerText);
		timerText.setText("Timer Off");

		messageText = (EditText) findViewById(R.id.messageText);

		problemText = (EditText) findViewById(R.id.problemText);

		// Add event listeners to buttons.
		final Button button0 = (Button) findViewById(R.id.button_0);
		button0.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				inputTextField.append(button0.getText());
			}
		});

		final Button button1 = (Button) findViewById(R.id.button_1);
		button1.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				inputTextField.append(button1.getText());
			}
		});

		final Button button2 = (Button) findViewById(R.id.button_2);
		button2.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				inputTextField.append(button2.getText());
			}
		});

		final Button button3 = (Button) findViewById(R.id.button_3);
		button3.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				inputTextField.append(button3.getText());
			}
		});

		final Button button4 = (Button) findViewById(R.id.button_4);
		button4.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				inputTextField.append(button4.getText());
			}
		});

		final Button button5 = (Button) findViewById(R.id.button_5);
		button5.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				inputTextField.append(button5.getText());
			}
		});

		final Button button6 = (Button) findViewById(R.id.button_6);
		button6.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				inputTextField.append(button6.getText());
			}
		});

		final Button button7 = (Button) findViewById(R.id.button_7);
		button7.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				inputTextField.append(button7.getText());
			}
		});

		final Button button8 = (Button) findViewById(R.id.button_8);
		button8.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				inputTextField.append(button8.getText());
			}
		});

		final Button button9 = (Button) findViewById(R.id.button_9);
		button9.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				inputTextField.append(button9.getText());
			}
		});

		final Button button_backspace = (Button) findViewById(R.id.button_backspace);
		button_backspace.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {

				String currentText = inputTextField.getText().toString();

				if (currentText.length() > 0) {
					currentText = currentText.substring(0,
							currentText.length() - 1);

					inputTextField.setText("");

					inputTextField.append(currentText); // Place cursor at end
														// of text.
				}

			}
		});

		final Button button_enter = (Button) findViewById(R.id.button_enter);
		button_enter.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {

				enter();

			}
		});

		final Button button_configure = (Button) findViewById(R.id.button_configure);
		button_configure.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {

				Intent settingsActivity = new Intent(getBaseContext(),
						PreferencesActivity.class);
				startActivity(settingsActivity);

			}
		});

		button_pause_resume = (ToggleButton) findViewById(R.id.button_pause_resume);
		button_pause_resume
				.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
					public void onCheckedChanged(CompoundButton buttonView,
							boolean isChecked) {
						if (isChecked) {
							// The toggle is enabled
							if (timerManager != null) {
								timerManager.setPaused(true);
							}
							problemTextPauseStorage = problemText.getText();
							problemText.setText("PAUSED");
							speak("Paused.");
						} else {
							// The toggle is disabled
							if (timerManager != null) {
								timerManager.setPaused(false);
							}
							problemText.setText(problemTextPauseStorage);
							speak("Resumed.");
						}
					}
				});

/*
		final Button button_help = (Button) findViewById(R.id.button_help);
		button_help.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {

				Fold fold = foldsMap.get("help");

				if (fold == null) {
					String message = "Help information is missing.";

					Log.e(TAG, message);

					Toast.makeText(MathPiperExerciseActivity.this, message,
							Toast.LENGTH_SHORT).show();

					return;
				}

				String helpString = fold.getContents();

				helpString = helpString.replace("\"", "");

				speak(helpString);

			}
		});

		final Button button_hint = (Button) findViewById(R.id.button_hint);
		button_hint.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
				hint();
			}
		});
*/
		button_start_stop = (ToggleButton) findViewById(R.id.button_start_stop);
		;
		button_start_stop
				.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
					public void onCheckedChanged(CompoundButton buttonView,
							boolean isChecked) {
						if (isChecked) {
							// The toggle is enabled.

							started = true;

							if (currentQuestionManager != null) {
								currentQuestionManager.stop();
							}

							startTimer();

							questionAsk();

						} else {
							// The toggle is disabled.

							if (timerManager != null) {
								timerManager.stop();
							}

							endSession();

						}
					}
				});
/*
		final Button button_state = (Button) findViewById(R.id.button_state);
		button_state.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {

				// String input = inputTextField.getText().toString();

				EvaluationResponse response = exerciseService
						.evaluate("TableForm(State());");

				String result;

				if (response.isExceptionThrown()) {
					result = response.getException().getMessage();

					Log.e(TAG, result);
				} else {
					result = response.getSideEffects();
				}
				messageText.append("*** MathPiper Variables ***\n");
				messageText.append(result + "\n");

				StringBuilder stringBuilder = new StringBuilder();
				Map map = preferences.getAll();
				Set keySet = map.keySet();
				List keyList = new ArrayList(keySet);
				Collections.sort(keyList);
				for (Object key : keyList) {
					Object value = map.get(key);
					stringBuilder.append(key + " : " + value + "\n");
				}
				messageText.append("*** Preferences Variables ***\n");
				messageText.append(stringBuilder.toString() + "\n");

				messageText.append("*** Instance Variables ***\n");
				messageText.append("Utterance Number: " + utteranceNumber
						+ "\n");
				messageText.append("Problem Delay: " + problemDelay + "\n");

				messageText.append("\n");

				messageText.append("------------------------\n");

			}
		});
*/
	}// end method.

	private void initialize() throws NameNotFoundException {

		// Unassign all variables in the MathPiper interpreter.
		EvaluationResponse response = exerciseService.evaluate("Unassign(*);");
		String result;

		if (response.isExceptionThrown()) {
			result = response.getException().getMessage();

			Log.e(TAG, result);

			Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
			return;
		} else {
			result = response.getResult();
		}

		InputStream inputStream;
		try {

			String mpwFile = this.getIntent().getStringExtra(
					"org.mathpiper.ui.gui.mpwFile");

			// String mpwFile = "remember_numbers.mpw";

			inputStream = getAssets().open(mpwFile);

			foldsMap = org.mathpiper.test.MPWSFile.getFoldsMap(mpwFile,
					inputStream);

		} catch (Throwable e) {
			Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
			Log.e(TAG, e.getMessage());
		}

		// =================== Configure.

		Fold fold = foldsMap.get("configuration");

		if (fold == null) {
			String message = "Configuration information is missing.";

			Log.e(TAG, message);

			Toast.makeText(MathPiperExerciseActivity.this, message,
					Toast.LENGTH_SHORT).show();

			return;
		}

		String codeText = fold.getContents();

		response = exerciseService.evaluate(codeText);

		if (response.isExceptionThrown()) {
			result = response.getException().getMessage();

			Log.e(TAG, result);

			Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
			return;
		} else {
			result = response.getResult();
		}

		// =================== Information.
		fold = foldsMap.get("information");

		if (fold == null) {
			String message = "Exercise information is missing.";

			Log.e(TAG, message);

			Toast.makeText(MathPiperExerciseActivity.this, message,
					Toast.LENGTH_SHORT).show();

			return;
		}

		codeText = fold.getContents();

		response = exerciseService.evaluate(codeText);

		if (response.isExceptionThrown()) {
			result = response.getException().getMessage();

			Log.e(TAG, result);

			Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
			return;
		} else {
			result = response.getResult();
		}

		// ================== Exercise engine.
		fold = foldsMap.get("ExerciseEngine");

		if (fold == null) {
			String message = "The exercise engine is missing.";

			Log.e(TAG, message);

			Toast.makeText(MathPiperExerciseActivity.this, message,
					Toast.LENGTH_SHORT).show();

			return;
		}

		codeText = fold.getContents();

		response = exerciseService.evaluate(codeText);

		if (response.isExceptionThrown()) {
			result = response.getException().getMessage();

			Log.e(TAG, result);

			Toast.makeText(this, result, Toast.LENGTH_SHORT).show();

			return;

		} else {
			result = response.getResult();
		}

		initializePreferences();

		try {
			// exerciseLogOutputStream = openFileOutput(exerciseLogFileName, Context.MODE_PRIVATE | Context.MODE_APPEND);
			// todo:tk:Do not allow the application to run without access to the log file.
			exerciseLogOutputWriter = new FileWriter(exerciseLogFilename, true);
		} catch (Exception e) {
			Log.e(TAG, e.getMessage());

			Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
		}

		PackageManager manager = getApplicationContext().getPackageManager();
		PackageInfo info = manager.getPackageInfo(
				getApplicationContext().getPackageName(), 0);
		String appVersion = info.versionName;
		;
		log("[\"Tag\",\"VersionInfo\"],[\"AppVersion\",\"" + appVersion +"\"],[\"MathpiperVersion\",\"" + org.mathpiper.Version.version() + "\"]");
	}

	private long timeStamp() {

		Date date = Calendar.getInstance().getTime();

		return date.getTime();
	}

	private void startTimer() {

		questionNumber = 0;

		correctOnFirstTryCount = 0;

		StringBuilder sb = new StringBuilder();
		sb.append("[\"Tag\",\"SessionStart\"]");
		log(sb.toString());

		speak("New session.");

		sb = new StringBuilder();
		sb.append("[\"Tag\",\"ExerciseInformation\"]");
		String exerciseName = exerciseService.getGlobalString("exerciseName");
		if (exerciseName == null)
			exerciseName = "\"<UNNAMED>\"";
		sb.append(preferencesToMathPiperCode("Name:String", exerciseName));

		String exercisePackage = exerciseService.getGlobalString("exercisePackage");
		if (exerciseName == null)
			exerciseName = "\"<UNNAMED>\"";
		sb.append(preferencesToMathPiperCode("Package:String", exercisePackage));

		String exerciseVersion = exerciseService
				.getGlobalString("exerciseVersion");
		if (exerciseVersion == null)
			exerciseVersion = "\"<UNVERSIONED>\"";
		sb.append(preferencesToMathPiperCode("Version:String", exerciseVersion));
		log(sb.toString());



		sb = new StringBuilder();
		sb.append("[\"Tag\",\"Preferences\"]");
		Map preferencesMap = preferences.getAll();
		Set preferencesKeySet = preferencesMap.keySet();
		for (Object key : preferencesKeySet) {
			String keyString = key.toString();

			keyString = keyString.substring( keyString.lastIndexOf(".") + 1, keyString.length());

			String value = preferencesMap.get(key).toString();

			sb.append(preferencesToMathPiperCode(keyString, value));

		}
		log(sb.toString());



		int minutes = Integer.parseInt(preferences.getString(exercisePackage
				+ "." + "timer:Integer", "0"));

		button_pause_resume.setEnabled(true);

		if (minutes != 0) {
			if (timerManager != null) {
				timerManager.stop();
			}

			timerManager = new TimerManager(timerHandler, minutes);

			timerHandler.postDelayed(timerManager, 0);
		}
	}

	private void questionAsk() {
		EvaluationResponse response = exerciseService
				.evaluate("QuestionAsk();");

		String result;

		if (response.isExceptionThrown()) {
			result = response.getException().getMessage();

			Log.e(TAG, result);
		} else {
			result = response.getResult();
		}

		result = result.substring(1, result.length() - 1);

		List<String> currentQuestion = new ArrayList<String>();

		Matcher m = p.matcher(result);

		while (m.find()) {
			String problem = result.substring(m.start(), m.end());
			problem = problem.replace("\"", "");
			currentQuestion.add(problem);
		}

		messageText.append("\n-------------------- Question "
				+ ++questionNumber + "\n");

		log("[\"Tag\",\"NewQuestion\"]" + ",[\"Question\"," + result + "]"
				+ ",[\"Number\"," + questionNumber + "]");

		newQuestionFlag = true;

		String exercisePackage = exerciseService
				.getGlobalString("exercisePackage");

		problemDelay = Long.parseLong(preferences.getString(exercisePackage
				+ "." + "problemDelay:Integer", "2000"));

		if (currentQuestionManager != null) {
			currentQuestionManager.stop();
		}

		currentQuestionManager = new QuestionManager(questionHandler,
				currentQuestion);

		questionHandler.postDelayed(currentQuestionManager, 0);

	}

	private String preferencesToMathPiperCode(String keyString, String value) {

		String[] keyStringArray = keyString.split(":");
		String keyName = keyStringArray[0];
		String type = keyStringArray[1];

		if(value.equals("true"))
		{
			value = "True";
		}

		if(value.equals("false"))
		{
			value = "False";
		}

		StringBuilder sb = new StringBuilder();

		sb.append(",[\"");
		sb.append(keyName);
		sb.append("\",");
		if (type.contains("String")) {
			sb.append("\"");
		}

		sb.append(value);

		if (type.contains("String")) {
			sb.append("\"");
		}

		sb.append("]");

		return sb.toString();
	}

	private void log(String logInformation) {
		final StringBuilder sb = new StringBuilder();

		sb.append("[");
		sb.append(logInformation);
		sb.append(",");
		sb.append("[\"Timestamp\"," + timeStamp() + "]");
		sb.append("];\n");

		try {
			this.exerciseLogOutputWriter.write(sb.toString());
			this.exerciseLogOutputWriter.flush();
		} catch (IOException e) {
			Log.e(TAG, e.getMessage());
		}
	}

	private void hint() {
		EvaluationResponse response = exerciseService
				.evaluate("QuestionHint();");

		String result;

		if (response.isExceptionThrown()) {
			result = response.getException().getMessage();

			Log.e(TAG, result);
		} else {
			result = response.getResult();
		}

		String hint = result.replace("\"", "");

		speak(hint);

	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);

	}

	private void enter() {
		String input = inputTextField.getText().toString();

		if (input.equals("")) {

			if (this.timerManager != null && this.timerManager.isPaused()) {
				timerManager.setPaused(false);
			}

			if (started == true) {

				log("[\"Tag\",\"ReplayedQuestion\"]");

				if (currentQuestionManager != null) {
					currentQuestionManager.stop();
				}
				questionHandler.postDelayed(currentQuestionManager, 0);
			} else {
				// started = true;

                button_start_stop.setChecked(true);

				// startTimer();

				// questionAsk();
			}

			return;
		} else if (input.equals("*")) {
			hint();

			return;
		}

		if (currentQuestionManager != null) {
			currentQuestionManager.stop();
		}

		EvaluationResponse response = exerciseService.evaluate("QuestionCheck('(" + input + "));");

		String result;

        String exceptionMessage = null;

		if (response.isExceptionThrown()) {
			result = response.getException().getMessage();
			Log.e(TAG, result);

            //Toast.makeText(this, "The answer must be a numeral.", Toast.LENGTH_SHORT).show();

            log("[\"Tag\",\"Exception\"]" + ",[\"Message\",\"" + result + "\"]" + ",[\"Answer\",\"" + input + "\"]");

            exceptionMessage = result.substring(0, result.indexOf("."));

		} else {
			result = response.getResult();

            log("[\"Tag\",\"Answer\"]" + ",[\"Response\",\"" + input + "\"],[\"Result\"," // todo:tk:should answer and result be strings?
                    + result + "]" + ",[\"QuestionNumber\"," + questionNumber + "]");

            speak(input);
		}

		inputTextField.setText("");

		if (result.equals("True")) {

			if (newQuestionFlag == true) {
				this.correctOnFirstTryCount++;
			}

			messageText.append("Correct.\n");

			speak("correct");

			questionAsk();
		} else {
            String incorrectMessage;
            if(exceptionMessage != null)
            {
                incorrectMessage = exceptionMessage;
            }
            else
            {
                incorrectMessage = "Incorrect";
            }
			messageText.append(incorrectMessage + ".\n");

			this.newQuestionFlag = false;

			speak(incorrectMessage);

			questionHandler.postDelayed(currentQuestionManager, 0);
		}

	}

	@Override
	public void onDestroy() {
		super.onDestroy();

		preferences.unregisterOnSharedPreferenceChangeListener(this);

		if (timerManager != null) {
			timerManager.stop();
		}

		if(started == true)
		{
			endSession();
		}

		exerciseService.removeOnUtteranceCompletedListener();

		this.unbindService(this);

		try {
			this.exerciseLogOutputWriter.flush();
			exerciseLogOutputWriter.close();
		} catch (IOException e) {
			Log.e(TAG, e.getMessage());
		}
	}

	@Override
	public void onUtteranceCompleted(String utteranceId) {
System.out.println(utteranceId);
		if (utteranceId.equals("question")) {
		    	listen();

			log("[\"Tag\",\"EndOfQuestionUtterance\"]" + ",[\"QuestionNumber\","
					+ questionNumber + "]");

			questionJustAsked = true;
		}

		Log.d(TAG, "Utterance " + utteranceNumber++ + " complete.");

	}

	private void endSession() {
		timerText.setText("Timer Off");

		button_pause_resume.setEnabled(false);

		button_start_stop.setEnabled(true);

		started = false;

		log("[\"Tag\",\"SessionEnd\"],[\"QuestionCount\"," + questionNumber
				+ "],[\"CorrectOnFirstTryCount\"," + correctOnFirstTryCount + "]");

		messageText.append("End of session. Question count: " + questionNumber
				+ ", Correct on first try count: " + correctOnFirstTryCount
				+ "\n\n");

		speak("End of session.");

        if(speechRecognitionIntent != null) {
            PendingIntent.getBroadcast(getApplicationContext(), 0, speechRecognitionIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT).cancel();
        }

	}


    private class QuestionManager implements Runnable {

		private Handler handler;

		private List<String> problems;

		private int index = 0;

		QuestionManager(Handler handler, List<String> problems) {
			this.handler = handler;
			this.problems = problems;
		}

		public void run() {

            if(problems.size() > 1) {
                messageText.append(index + 1 + "/" + problems.size() + ", ");
            }

			problemText.setText(problems.get(index));

			speakQuestion(problems.get(index));

			index++;

			if (index != problems.size()) {
				handler.postDelayed(this, problemDelay);
			} else {
				messageText.append("\n");
				stop();
			}

            inputTextField.requestFocus();
		}

		public boolean isRunning() {
			return index == 0;
		}

		public void stop() {
			index = 0;

			handler.removeCallbacks(this);
		}
	}

	private class TimerManager implements Runnable {

		private Handler handler;

		private String[] problems;

		private int seconds = 0;

		private boolean paused = false;

		TimerManager(Handler handler, int minutes) {
			this.handler = handler;
			this.seconds = minutes * 60;
		}

		public void run() {

			if (paused) {
				handler.postDelayed(this, 1000);

				return;
			}

			int hours = (int) seconds / 3600;
			int remainder = (int) seconds - hours * 3600;
			int mins = remainder / 60;
			remainder = remainder - mins * 60;
			int secs = remainder;

			timerText.setText(hours + ":" + mins + ":" + secs);

			if (seconds != 0) {

				seconds--;

				handler.postDelayed(this, 1000);
			} else {

				log("[\"Tag\",\"TimerFinished\"]");

				speak("Time is up.");

				endSession();

				stop();
			}
		}

		public void stop() {
			seconds = 0;

			handler.removeCallbacks(this);
		}

		public boolean isPaused() {
			return paused;
		}

		public void setPaused(boolean paused) {
			this.paused = paused;

			log("[\"Tag\",\"TimerPausedAction\"],[\"State\"," + paused + "]");

			if (paused) {
				speak("Paused.");
			} else {
				speak("Resumed.");
			}
		}

	}

	private void speak(String text) {

		if (isSpeech) {

			text = text.replace("*", "times");
			text = text.replace("-", "minus");
			text = text.replace("/", "divided by");

			exerciseService.speak(text);
		}
	}

	private void speakQuestion(String text) {

		if (isSpeech) {

			text = text.replace("*", "times");
			text = text.replace("-", "minus");
			text = text.replace("/", "divided by");

			exerciseService.speak(text,"question");
		}
	}

	private void listen()
	{
		if(started && isListen)
		{

		    startActivityForResult(speechRecognitionIntent, REQUEST_CODE);
            // this.speechRecognizer.startListening(this.speechRecognitionIntent);
		}
	}

	@Override
	public void onServiceConnected(ComponentName name, IBinder service) {
		LocalBinder binder = (LocalBinder) service;
		exerciseService = binder.getService();

		exerciseService.setOnUtteranceCompletedListener(this);

		messageText.append("Initializing...");
		speak("initializing");

		try {
			initialize();
			speak("ready");

			messageText.append("ready.\nMathPiper "
					+ org.mathpiper.Version.version() + ".\n");
		} catch (Exception e) {
			speak("Initialization error.");

			Log.e(TAG, e.getMessage());

			StringWriter errors = new StringWriter();
			e.printStackTrace(new PrintWriter(errors));
			Log.d(TAG, errors.toString());

			messageText.append(e.getMessage() + ".\n");
		}

	}

	@Override
	public void onServiceDisconnected(ComponentName name) {
		// TODO Auto-generated method stub

	}


     // Speech recognition.
	 @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {


         if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {

             ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
             for (String s : matches) {
                 messageText.append("LISTENING RESULT: " + convertWord(s) + "\n");
             }
             messageText.append("\n");

             inputTextField.setText(convertWord(matches.get(0)));

             enter();
         }

         super.onActivityResult(requestCode, resultCode, data);
     }

    private String convertWord(String word) {
        word = word.toLowerCase();

        String convertedWord = wordMap.get(word);

        if(convertedWord == null) {
            return word;
        }
        else
        {
            return(convertedWord);
        }
    }


// SpeechRecognizer API.
    @Override
    public void onReadyForSpeech(Bundle params) {
        final ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_MUSIC, 100);
        tg.startTone(ToneGenerator.TONE_PROP_BEEP);
    }

    @Override
    public void onBeginningOfSpeech() {

    }

    @Override
    public void onRmsChanged(float rmsdB) {

    }

    @Override
    public void onBufferReceived(byte[] buffer) {

    }

    @Override
    public void onEndOfSpeech() {

    }

    @Override
    public void onError(int error) {

    }

    @Override
    public void onResults(Bundle results) {

    }

    @Override
    public void onPartialResults(Bundle partialResults) {

    }

    @Override
    public void onEvent(int eventType, Bundle params) {

    }

}// end class.